Lab 10 - Mapping

FOSSBot4AI - Template

IRiM and Fossbot4AI logos

1. Activity Identity

Activity title Introduction to Robotics
Topic Robotics / ROS 2 / Mapping
Authors Institute of Robotics and Machine Intelligence 
Dominik Belter, Jakub Chudziński, Marcin Czajka, Kamil Młodzikowski
Target learners Bachelor
Estimated duration 1.5 hour
Difficulty level Intermediate
FOSSBot environment Simulator (can be also performed on a real robot)
Licence CC BY 4.0

2. Learning Objectives and Competences

ID Learning outcome Related competances Assessment evidence
LO1 Students will be able to create and build custom ROS2 service interfaces (.srv) and implement a service server node using Python. ROS2 architecture / ROS2 communication / Software engineering Code and results table
LO2 Students will be able to translate real-world Cartesian coordinates (X, Y) into 1D array indices to interpret 2D Occupancy Grid maps. Spatial reasoning / Data structure manipulation Results table for various coordinates
LO3 Students will be able to develop a segment-checking algorithm to determine if a straight-line path between two points intersects with known obstacles. Algorithm design / Collision detection Python code from Step 4 and results table.

3. Prerequisites

4. Required Material and Setup

Category Item Version / Quantity Notes
Hardware Workstation 1 per student Linux PC with at least 8 GB RAM. An NVIDIA GPU with the nvidia-container-toolkit is recommended so that start_container.sh can use GPU passthrough; on a machine without an NVIDIA GPU, use the included start_container_no_gpus.sh instead.
Software Docker Engine 24.0 or newer Pre-installed on the lab workstations.
Software FOSSBotEduSim simulator latest from main branch Cloned from https://github.com/LRMPUT/FOSSBotEduSim (instructions available in the repository). The repository ships its own Dockerfile that derives from the official osrf/ros:jazzy-desktop-full image and pre-installs every ROS 2 package the lab needs.

5. Safety, Ethics and Accessibility Notes

If using a physical robot: When teleoperating the physical robot for mapping, follow the robot to ensure it does not bump into anything or fall down. Ensure batteries are safely charged.

6. Scenario and Problem Statement

Autonomous mobile robots need to navigate through environments that may contain obstacles. To do this, they often rely on occupancy grid maps, which represent the environment as a grid of cells, each indicating the probability of it being free or occupied, or in a case it wasn’t seen, unknown.

In this lab, you will use a simulated robot to create an occupancy grid map of a simple environment. You will then implement a ROS2 service that checks the occupancy of specific points in the map and extends it to check if a straight-line segment between two points intersects with any occupied or unknown cells.

7. Lab Workflow

Phase Student action Expected output Time
1. Prepare Install/check environment Ready-to-run setup [5 min]
2. Setup Configure simulator Simulator running [5 min]
3. Mapping Control the robot to collect the data and build the map An occupancy grid map [10 min]
4. First service Write a ROS2 node, that will use service to check occupancy for a point Results table [20 min]
5. Segment checking service Modify the ROS2 node, to enable segment checking Code and results table [40 min]
6. Reflect Answer synthesis questions Short analysis [10 min]

8. Step-by-Step Instructions

Step 1 - Environment preparation

  1. Check if you have the FOSSBot ros2 environment set up (if not, install it following the instructions available in the repository).

  2. Start the FOSSBotEduSim container.

bash start_container.sh

or if you are on a machine without an NVIDIA GPU, use:

bash start_container_no_gpus.sh
  1. [Only if you’re not using the Docker container] Open a terminal and source the ROS2 setup file:
source /opt/ros/jazzy/setup.bash

The Docker image added the ROS2 setup file to your .bashrc, so if you are using the container, you don’t need to run this command.

Step 2 - Simulator configuration and mapping

  1. Launch the FOSSBot simulator with the single wall world:
ros2 launch fossbot_educational_description single.launch.py world:=sample_rooms.sdf
  1. Ensure that in Gazebo you can see the robot, the blue walls and that RViz is running.

  2. Open a new terminal and enter the docker container. Launch the SLAM toolbox to start mapping the environment:

ros2 launch slam_toolbox online_async_launch.py use_sim_time:=True

Note: More about SLAM will be explained in the Lab 12. For now, it is enough to know that the robot will use its position and sensors to create a map of the environment.

  1. Open another new terminal and enter the docker container. Start teleoperation of the robot using the keyboard:
ros2 run teleop_twist_keyboard teleop_twist_keyboard
  1. Drive the robot around to create a map of the environment. Make sure that all areas are covered (everything inside the blue walls should be white on the map).

  2. Save the map to a file:

ros2 run nav2_map_server map_saver_cli -f /fossbot_ros2/ws_fossbot/sample_rooms_map

❗❗IMPORTANT:❗❗ The map will be needed for the next lab, so make sure to save it in a safe place.

Expected result:

The complete map should look like this: Map of the environment

Step 3 - Occupancy checking and ROS2 services

❗❗IMPORTANT:❗❗: Do not close the terminal with the simulator and the SLAM toolbox. It will be needed in the next steps.

  1. In ROS2 services allow to send a request and receive a response from a node (in this case the node will receive coordinates of a point and will return the occupancy value stored in the map for that point).

Let’s start with defining a new service type. Open a new terminal and enter the docker container. Create a new ROS2 package for the service definition:

cd /fossbot_ros2/ws_fossbot/src
ros2 pkg create --build-type ament_cmake map_occupancy_interfaces
mkdir -p /fossbot_ros2/ws_fossbot/src/map_occupancy_interfaces/srv
touch /fossbot_ros2/ws_fossbot/src/map_occupancy_interfaces/srv/CheckOccupancy.srv

Paste the following content into the CheckOccupancy.srv file:

geometry_msgs/Point point
---
bool occupied
string status

In the above service definition, the request contains a geometry_msgs/Point message with the coordinates of the point to check, and the response contains a boolean indicating whether the point is occupied and a string with a status message.

Now modify the package.xml file to add the necessary dependencies (paste this before tag):

  <depend>geometry_msgs</depend>
  <buildtool_depend>rosidl_default_generators</buildtool_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

Now open the CMakeLists.txt file and add the following lines (below find_package(ament_cmake REQUIRED)):

find_package(rosidl_default_generators REQUIRED)
find_package(geometry_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/CheckOccupancy.srv"
  DEPENDENCIES geometry_msgs
)

The rosidl_generate_interfaces macro generates the necessary code for the service based on the .srv file.

  1. Now it’s time to implement the node that will check the occupancy of the map. Create a new ROS2 package for the occupancy checker node:
cd /fossbot_ros2/ws_fossbot/src
ros2 pkg create --build-type ament_python map_occupancy_checker

Add a new Python file where you will implement the node that will check the occupancy of the map:

touch /fossbot_ros2/ws_fossbot/src/map_occupancy_checker/map_occupancy_checker/occupancy_checker.py

Copy the following code snippet into the occupancy_checker.py file:

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from nav_msgs.msg import OccupancyGrid
from geometry_msgs.msg import Point
# Import the service definition that we created earlier
from map_occupancy_interfaces.srv import CheckOccupancy

class OccupancyChecker(Node):
    def __init__(self):
        super().__init__('occupancy_checker')
        # Create a subscription to the /map topic to receive occupancy grid messages
        self.subscription = self.create_subscription(
            OccupancyGrid,
            '/map',
            self.map_callback,
            10)
        self.subscription
        # A variable to store the occupancy grid data
        self._map = None
        # A variable to store the map info data
        self._map_info = None

    def map_callback(self, msg):
        self._map = msg.data
        if self._map_info is None:
            self._map_info = msg.info

def main(args=None):
    rclpy.init(args=args)
    occupancy_checker = OccupancyChecker()
    rclpy.spin(occupancy_checker)
    occupancy_checker.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Update the setup.py file to include the new node in the entry points section:

    entry_points={
        'console_scripts': [
            'occupancy_checker = map_occupancy_checker.occupancy_checker:main'
        ],
    },

Modify the package.xml file to add the necessary dependencies:

  <exec_depend>rclpy</exec_depend>
  <exec_depend>nav_msgs</exec_depend>
  <exec_depend>geometry_msgs</exec_depend>
  <exec_depend>map_occupancy_interfaces</exec_depend>
  1. Add a service server to the occupancy_checker.py node that will handle requests to check the occupancy of a point in the map. Update the OccupancyChecker class as follows:
        self.service_server = self.create_service(CheckOccupancy, 
                                                  'check_occupancy', 
                                                  self.check_occupancy_callback)
    def check_occupancy_callback(self, request, response):
        if self._map is None or self._map_info is None:
            response.occupied = True
            response.status = "Map data not available yet."
            return response

        # REMOVE THIS WHEN YOU IMPLEMENT THE LOGIC TO CHECK IF THE POINT IS OCCUPIED
        else:
            response.occupied = True
            response.status = "The occupancy check logic is not implemented yet."
            return response
  1. Implement the occupancy checking logic in the check_occupancy_callback method. You will need to convert the point coordinates from world frame to map frame and then check the occupancy value in the occupancy grid data. Take the pseudocode below as a reference:
map_x = request_x - map_origin_x
map_y = request_y - map_origin_y
grid_x = int(map_x / map_resolution)
grid_y = int(map_y / map_resolution)
out_of_bounds = (grid_x < 0 or grid_x >= map_width or grid_y < 0 or grid_y >= map_height)
Occupancy grid data visualization

Index of the orange cell in the occupancy grid data can be calculated as follows:

index_orange = 0 * 5 + 1 = 1,

for the green cell:

index_green = 2 * 5 + 0 = 10,

and for the blue cell:

index_blue = 4 * 5 + 2 = 22.
  1. Build the packages and source the setup file:
cd /fossbot_ros2/ws_fossbot
colcon build --symlink-install --packages-select map_occupancy_interfaces map_occupancy_checker && source install/setup.bash
  1. Launch the occupancy checker node:
ros2 run map_occupancy_checker occupancy_checker
  1. Open a new terminal and enter the docker container. Check if the service is available:
ros2 service list

You should see the /check_occupancy service in the list.

  1. Call the service to check the occupancy of the map at a specific point:
ros2 service call /check_occupancy map_occupancy_interfaces/srv/CheckOccupancy "{point: {x: 0.0, y: 0.0, z: 0.0}}"

Check the response to see if the point is occupied or free. Try calling the service with different points and observe the results. Save the results in an observation table.

Tip: In RViz you can visualize axes of the global frame. Set the Fixed Frame to map and add the Axes display type. This will help you to determine the coordinates of the points you want to check. By default, the length of the axes is 1 meter, so you can use the grid in RViz to estimate the coordinates of the points. Red axis is the X axis, green axis is the Y axis and blue axis is the Z axis.

Expected result: You should be able to call the service and receive a response indicating whether the point is occupied or free. The response should also include a status message to clarify the result.

Step 4 - Grid occupancy checking in motion planning

In the previous step, you implemented a service that checks the occupancy of a point in the map. Now you will modify the code to enable collision checking for a segment. We assume that the segment is a straight line between two points and that we want to check the path for a set of points not a robot (a robot has a size, so we would have to consider its dimensions). The service should: - receive two points as input (start and end of the segment), - check the occupancy of those points, - check if the segment between those points intersects with any occupied or unknown cells in the occupancy grid, - return a boolean indicating whether the segment is free or not, and one of the following status messages: - “Map data not available yet.” if the map data was not received yet, - “The segment is free.” if the segment is fully free, - “One of the starting points is occupied or unknown.” if one of the starting points is occupied or unknown, - “The segment intersects with an occupied or unknown cell.” if the segment intersects with an occupied or unknown cell.

Tip: To check if the segment intersects with any occupied or unknown cells, you should generate a set of points along the segment and check the occupancy of each point. You can simply sample the segment every grid_resolution/10 or use Bresenham’s line algorithm.

Expected result: You should be able to call the service with two points and receive a response indicating whether the segment is free or not, along with an appropriate status message.

9. Analysis Questions

  1. When is it better to use a service instead of a topic in ROS2?

  2. What could happen if you sample the segment too sparsely when checking for occupancy? How could this affect the results?

  3. As mentioned in the step 4, the service is used to check if a segment of points is free or not. How could you modify the service or the map itself to account for the size of a robot when checking for collisions along a path?

10. Submission Requirements

11. References and Open Licence

[Add references, datasets, libraries, repositories, and licensing information. Mention original authors where applicable.] - Writing a Simple Py Service and Client in ROS2: https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Service-And-Client.html, - Nav2 Documentation: https://nav2.org/.

The Creative Commons Attribution 4.0 International (CC BY 4.0) license allows users to share, copy, distribute, and adapt the work, even for commercial purposes, as long as proper credit is given to the original creator.

EU funding disclaimer

Funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Education and Culture Executive Agency (EACEA). Neither the European Union nor EACEA can be held responsible for them.